home *** CD-ROM | disk | FTP | other *** search
/ Ian & Stuart's Australian Mac: Not for Sale / Another.not.for.sale (Australia).iso / fade into you / getting there / WWW / WebStat 2.3.4 / WebStat.cp < prev    next >
Text File  |  1994-10-11  |  33KB  |  1,240 lines

  1. /*
  2. ** File:        WebStat.cp
  3. **
  4. **                A transmission statistics summary program for MacHTTP log output
  5. **
  6. ** Author:        Philip Harvey (phil@nsun.phy.queensu.ca)
  7. **                Physics Dept
  8. **                Queen's University
  9. **
  10. ** Revisions:    03/24/94 - V 1.0    - original
  11. **                03/25/94 - V 1.1    - fixed array indexing problem
  12. **                03/28/94 - V 1.2    - added anchors for lists; converted code to C++
  13. **                03/29/94 - V 1.3    - added exclusion option and application icon
  14. **                                    - changed all byte counts from long to double
  15. **                03/30/94 - V 1.4    - changed subdomain list to not reverse numerical entries
  16. **                                    - placed numerical entries at end of list
  17. **                03/31/94 - V 1.5    - added config file and moved exclusions into config
  18. **                04/04/94 - V 1.6    - added ADDRESSES Long/Short option
  19. **                                    - changed MESSAGES syntax from True/False to On/Off
  20. **                                    - added DOMAIN command to config file
  21. **                04/08/94 - V 1.7     - added comprehensive list of Domain names to config file
  22. **                                    - changed spacing to accomodate longer domain names
  23. **                04/11/94 - V 1.7.1    - delete trailing '/' if found in file names
  24. **                04/14/94 - V 1.8     - ignored improperly formatted lines in log file
  25. **                05/13/94 - V 1.9    - added SUMMARIZE options; changed to Symantec C++ 7.0
  26. **                                  - fixed quirk where days with no transfers were ignored
  27. **                06/06/94 - V 2.0    - truncate filenames at '$' because of new cgi formatting
  28. **                                    - added check on time_t values passed to GetNumDays()
  29. **                                    - added format file (replaces SUMMARIZE option of V1.9)
  30. **                08/11/94 - V 2.1    - removed limit on number of EXCLUDE and DOMAIN statements
  31. **                                    - sort numerical addresses properly
  32. **                09/27/94 - V 2.2    - added DNSLOOKUP option
  33. **                09/28/94 - V 2.3    - added progress indicator if MESSAGES Off
  34. **                                    - allows background processing if MESSAGES Off
  35. **                                    - fixed small bug in DNS lookups
  36. **                                    - Cmd-. aborts processing if MESSAGES Off
  37. **                                    - MESSAGES Off is now the default
  38. **                09/28/94 - V 2.3.1    - fixed problem in event handling during DNS lookups
  39. **                                    - fixed problem with output going to system folder broken in 2.3
  40. **                10/02/94 - V 2.3.2    - changed progress indicator; added stop button
  41. **                                    - re-fixed small bug in DNS lookups lost in 2.3.1
  42. **                10/03/94 - V 2.3.3    - fixed problem which could cause crash if stop button pressed
  43. **                                    - message dialog box is now properly activated
  44. **                10/11/94 - V 2.3.4    - progress bar now displays properly on B&W screens
  45. **
  46. ** Notes: For updates, the version number must be changed in the progress dialog title, in
  47. **          the program_name variable, and 3 times in the 'vers' resource.
  48. */
  49.  
  50. #include <ctype.h>
  51. #include <string.h>
  52. #include <stdlib.h>
  53. #include "WebStat.h"
  54. #include "TCPLib.h"
  55.  
  56. enum {
  57.     kStopButtonItem        = 1,
  58.     kStaticTextItem,
  59.     kProgressIndicatorItem
  60. };
  61.  
  62. extern "C" void HandleEvents(void);
  63.  
  64. /*
  65. ** static string definitions
  66. */
  67. static char *    config_file        = "WebStat.config";
  68. static char *    def_log_file    = "MACHTTP.LOG";
  69. static char *    def_out_file    = "WebStat.html";
  70. static char *    def_fmt_file    = "WebStat.format";
  71. static char *    program_name    = "WebStat 2.3.4";
  72.  
  73. /*
  74. ** The following constants specify the number of array elements by which the
  75. ** statistics lists should grow each time more memory is required:
  76. **
  77. ** If the numbers are too small memory may become fragmented due to excess
  78. ** reallocations and the program will run more slowly, and if the numbers are
  79. ** too large more memory than necessary will be used.
  80. **
  81. ** If memory and/or speed are a concern, these numbers can be optimized for
  82. ** your individual needs.
  83. */
  84. const long        kDateIncr        = 1000;        // for date list (this will do for 3 years)
  85. const long        kHourIncr        = 24;        // for hour list (only 24 hours in a day)
  86. const long        kWDayIncr        = 7;        // for weekday list (7 days in a week)
  87. const long        kDomainIncr        = 200;        // for domain list (alot of countries)
  88. const long        kSubDomainIncr    = 5000;        // for subdomain list (alot of computers)
  89. const long        kFileIncr        = 5000;        // for archive file list (alot of files)
  90.  
  91. const long         kExcludeIncr    = 100;        // for list of exclusions (EXCLUDE statements)
  92. const long        kCountryIncr    = 500;        // for country list (DOMAIN statements)
  93. const long        kDNSListIncr    = 500;        // for DNS lookup list
  94.  
  95. /*
  96. ** definitions of static member variables
  97. */
  98. unsigned long    StatList::totalFiles = 0;
  99. double            StatList::totalBytes = 0;
  100.  
  101. /*-----------------------------------------------------------------------------------------
  102. ** Utility routines
  103. */
  104.  
  105. /* strcmpi - compare two strings, ignoring case, and sorting special characters last */
  106. static int strcmpi(const char *str1, const char *str2)
  107. {
  108.     char    c1,c2;
  109.     
  110.     for (;; ++str1, ++str2) {
  111.         c1 = toupper(*str1);
  112.         c2 = toupper(*str2);
  113.         if (c1==c2) {
  114.             if (c1) continue;
  115.             break;
  116.         }
  117.         if (c1 < c2) {
  118.             if (!isupper(c1) && isupper(c2)) return(1);
  119.             else return(-1);
  120.         }
  121.         if (c1 > c2) {
  122.             if (isupper(c1) && !isupper(c2)) return(-1);
  123.             else return(1);
  124.         }
  125.     }
  126.     return(0);
  127. }
  128.  
  129. /* strcmpiPart - compare two strings, ignoring case, and stopping at the end of str1 */
  130. static int strcmpiPart(const char *str1, const char *str2)
  131. {
  132.     char    c1,c2;
  133.     
  134.     for (;; ++str1, ++str2) {
  135.         c1 = toupper(*str1);
  136.         c2 = toupper(*str2);
  137.         if (!c1) break;
  138.         if (c1 < c2) return(-1);
  139.         if (c1 > c2) return(1);
  140.     }
  141.     return(0);
  142. }
  143.  
  144. /* strcmpiWild - compare two strings, ignoring case, allow wildcards */
  145. static int strcmpiWild(const char *str1, const char *str2)
  146. {
  147.     char    c1,c2;
  148.     
  149.     for (;;) {
  150.         c1 = toupper(*str1);
  151.         c2 = toupper(*str2);
  152.         if (c1=='*' || c2=='*') return(0);
  153.         if (c1 < c2) return(-1);
  154.         if (c1 > c2) return(1);
  155.         if (!c1) break;
  156.         ++str1;
  157.         ++str2;
  158.     }
  159.     return(0);
  160. }
  161.  
  162. /* strtokQuote - strtok routine with quoted strings allowed */
  163. static char *strtokQuote(char *str, char *delim)
  164. {
  165.     char        *term;
  166.     static char    *next=0;
  167.     
  168.     if (!str && ((str=next)==0 || !(*str))) return((char *)0);
  169.     
  170.     /* skip all delimiter characters */
  171.     for (; *str; ++str) if (!strchr(delim,*str)) break;
  172.     
  173.     /* are we done? */
  174.     if (!*str) return(next=(char *)0);
  175.     
  176.     /* check for start of quoted string */
  177.     if (*str == '"') {
  178.         term = strchr(++str,'"');        // terminate at matching quote
  179.     } else {
  180.         /* find next delimiter */
  181.         for (term=str+1; *term; ++term) if (strchr(delim,*term)) break;
  182.     }
  183.     
  184.     if (term && *term) {
  185.         *term = 0;            // null terminate this token
  186.         next = term + 1;
  187.     } else {
  188.         next = 0;
  189.     }
  190.         
  191.     return(str);
  192. }
  193.  
  194. /*------------------------------------------------------------------------------------------
  195. ** Mac routines
  196. */
  197.  
  198. static Rect        progressItemRect;
  199. static long        progressItemValue = -1;
  200. static Boolean    macMode = FALSE;
  201. static Boolean    wne_impl;
  202. static Rect        dragRect;
  203.  
  204.  
  205. static void ToolBoxInit(void)
  206. {
  207.     const int    WNE_TRAP_NUM = 0x60;
  208.     const int    UNIMPL_TRAP_NUM = 0x9f;
  209.     
  210.     macMode = TRUE;
  211.     
  212.     InitGraf(&thePort);
  213.     InitFonts();
  214.     FlushEvents(everyEvent,0);
  215.     InitWindows();
  216.     InitMenus();
  217.     TEInit();
  218.     InitDialogs((ResumeProcPtr)0);
  219.     InitCursor();
  220.     
  221.     wne_impl = (NGetTrapAddress(WNE_TRAP_NUM,ToolTrap) !=
  222.                 NGetTrapAddress(UNIMPL_TRAP_NUM,ToolTrap));
  223.  
  224.     dragRect  = screenBits.bounds;
  225.     InsetRect(&dragRect, 4, 4);
  226. }
  227.  
  228. /* DrawMyItem - user item routine for progress indicator of wait dialog */
  229. static void pascal DrawMyItem(WindowPtr theWindow, short itemNum)
  230. {
  231.     int                tmp;
  232.     Rect            rect;
  233.     static RGBColor    colors[] = { { 0xcccc, 0xcccc, 0xffff },        // Baby blue
  234.                                   { 0x4444, 0x4444, 0x4444 },        // DarkGrey
  235.                                  };
  236.     
  237.     /* copy the item rect */
  238.     rect = progressItemRect;
  239.     
  240.     /* draw a black frame */
  241.     FrameRect(&rect);
  242.     
  243.     /* draw the indicator background */
  244.     InsetRect(&rect,1,1);
  245.     tmp = rect.left;
  246.     rect.left += (int)((rect.right-rect.left) * progressItemValue / 100);
  247.     RGBBackColor(colors);
  248.     EraseRect(&rect);
  249.     
  250.     /* draw the indicated percentage */
  251.     RGBForeColor(colors + 1);
  252.     rect.right = rect.left;
  253.     rect.left = tmp;
  254.     PaintRect(&rect);
  255. }
  256.  
  257. /* ShowProgress - show progress dialog */
  258. /* (if percent<0 dialog is removed) */
  259. void ShowProgress(int percent, Boolean forceShow)
  260. {
  261.     short                itemType;
  262.     Handle                itemHand, dlogHandle;
  263.     long                tmpLong;
  264.     SignedByte            savedState;
  265.     static DialogPtr    progressDlg = 0;
  266.     const long            kProgressDlgID = 200L;
  267.     const long            kWaitTime = 10;
  268.     static long            nextTime = 0;
  269.     Rect                rect;
  270.     GrafPtr                oldPort;
  271.  
  272.     if (percent < 0) {
  273.     
  274.         /* dispose the dialog if it exists */
  275.         if (progressDlg) {
  276.         
  277.             DisposDialog(progressDlg);
  278.             progressDlg = 0;
  279.             
  280.             progressItemValue = -1;
  281.             nextTime = 0;
  282.         }
  283.         
  284.     } else {
  285.     
  286.         /* create the dialog if it doesn't exist */
  287.         if (!progressDlg) {
  288.         
  289.             /* must lock resource so it isn't purged in GetNewDialog (!!)    */
  290.             /* (if it is purged, the PositionDialog() call would be undone) */
  291.             dlogHandle = GetResource('DLOG', kProgressDlgID);
  292.             savedState = HGetState(dlogHandle);
  293.             HLock(dlogHandle);
  294.             
  295.             /* position dialog and get handle */
  296.             progressDlg = GetNewDialog(kProgressDlgID,NULL,(WindowPtr)-1L);
  297.             
  298.             /* restore the resource memory state */
  299.             HSetState(dlogHandle, savedState);
  300.             
  301.             /* set the drawing proc for the progress indicator item */
  302.             GetDItem(progressDlg,kProgressIndicatorItem,&itemType,&itemHand,&progressItemRect);
  303.             SetDItem(progressDlg,kProgressIndicatorItem,itemType,(Handle)DrawMyItem,&progressItemRect);
  304.         }
  305.         
  306.         if (forceShow || (progressItemValue!=percent && TickCount()>nextTime)) {
  307.         
  308.             /* set the progress value */
  309.             progressItemValue = percent;
  310.             
  311.             /* set the next time to update this indicator */
  312.             nextTime = TickCount() + kWaitTime;
  313.             
  314.             if (forceShow) {
  315.             
  316.                 /* draw the whole dialog (to update text too) */
  317.                 DrawDialog(progressDlg);
  318.                 
  319.             } else {
  320.             
  321.                 /* update the progress indicator only */
  322.                 GetPort(&oldPort);
  323.                 SetPort(progressDlg);
  324.                 
  325.                 /* invalidate the progress item */
  326.                 InvalRect(&progressItemRect);
  327.                 
  328.                 SetPort(oldPort);
  329.             }
  330.         }
  331.     }
  332. }
  333.  
  334. /* Quit - Quit program */
  335. static void Quit(unsigned char *str)
  336. {
  337.     unsigned char    *pt;
  338.     const int        kMsgAlertID = 201;
  339.     const int        kLineLen = 50;
  340.     
  341.     if (macMode) {
  342.         ShowProgress(-1,TRUE);
  343.         ParamText(str,"\p","\p","\p");
  344.         NoteAlert(kMsgAlertID,0);
  345.         ExitToShell();
  346.     } else {
  347.         ++str;
  348.         while (*pt) {
  349.             pt = (unsigned char *)strchr((char *)str,'\r');
  350.             if (!pt) pt = (unsigned char *)strchr((char *)str,'\0');
  351.             /* keep lines to 80 chars or less */
  352.             if (pt - str > kLineLen) {
  353.                 pt = str + kLineLen;
  354.                 while (pt>str && !isspace(*pt)) --pt;
  355.                 if (pt == str) pt = str + kLineLen;
  356.             }
  357.             printf("%.*s\n",pt-str,str);
  358.             if (*pt == '\r') printf("\n");
  359.             str = pt + 1;
  360.         }
  361.         exit(1);
  362.     }
  363. }
  364.  
  365. /* HandleEvents - process Mac events */
  366. void HandleEvents()
  367. {
  368.     EventRecord        theEvent;
  369.     short            thePart;
  370.     WindowPtr        whichWindow;
  371.     DialogPtr        theDialog;
  372.     Boolean            abort = FALSE;
  373.     
  374.     /* process mac events */
  375.     if (wne_impl) {
  376.         WaitNextEvent(everyEvent, &theEvent, 0L, 0L);
  377.     } else {
  378.         SystemTask();
  379.         GetNextEvent(everyEvent, &theEvent);
  380.     }
  381.     
  382.     /* does the user want to abort? */
  383.     if (theEvent.what==keyDown && theEvent.modifiers&cmdKey && (theEvent.message&0xff)=='.') {
  384.         abort = TRUE;
  385.     } else if (IsDialogEvent(&theEvent)) {
  386.         if (DialogSelect(&theEvent, &theDialog, &thePart)) {
  387.             if (thePart == kStopButtonItem) abort = TRUE;
  388.         }
  389.     } else if (theEvent.what == mouseDown) {
  390.         /* handle window drags */
  391.         thePart = FindWindow(theEvent.where, &whichWindow);
  392.  
  393.         if (thePart == inDrag) {
  394.             SelectWindow(whichWindow);
  395.             DragWindow(whichWindow, theEvent.where, &dragRect);
  396.         }
  397.     }
  398.     if (abort) {
  399.         /* must close net to avoid problems if    */
  400.         /* quitting in the middle of a DNS lookup */
  401.         CloseResolver();
  402.     
  403.         Quit("\pWebStat processing halted!\rOutput file not written.\0");
  404.     }
  405. }
  406.  
  407. /*------------------------------------------------------------------------------------------
  408. ** Other routines
  409. */
  410.  
  411. /* MemoryError - print memory error message and terminate program */
  412. static void MemoryError()
  413. {
  414.     Quit("\pNot enough memory!\rPlease increase the memory size for WebStat using the Get Info option in the File menu.\0");
  415. }
  416.  
  417. /* ReverseAddress - reverse an IP address string */
  418. /* - kills source string                         */
  419. /* - returns pointer to last field in addr         */
  420. static char *ReverseAddress(char *src, char *dst)
  421. {
  422.     char    *pt;
  423.     char    *last_field = 0;
  424.     
  425.     dst[0] = 0;
  426.     
  427.     while ((pt=strrchr(src,'.')) != 0) {
  428.         if (!last_field) last_field = pt + 1;
  429.         strcat(dst,pt+1);
  430.         strcat(dst,".");
  431.         *pt = 0;
  432.     }
  433.     strcat(dst,src);
  434.     
  435.     return(last_field);
  436. }
  437.  
  438. /* GetIPAddress - get numerical address from string */
  439. static long GetIPAddress(char *hostname)
  440. {
  441.     int        i;
  442.     long    addr = 0;
  443.     
  444.     for (i=3;;) {
  445.         addr |= (long)atoi(hostname) << 8*i;
  446.         if (--i < 0) break;
  447.         hostname = strchr(hostname,'.') + 1;
  448.         if (!hostname) return(0L);
  449.     }
  450.     return(addr);
  451. }
  452.  
  453. /* ConvTime - get struct tm from time and date strings        */
  454. /* Time and date are in the form "09:57:56" and "03/28/94"    */
  455. /* otherwise a zero is returned.                            */
  456. static time_t ConvTime(char *time, char *date, struct tm *tms)
  457. {
  458.     /* do a quick check on the time and date formats so we can be    */
  459.     /* resonably certain that we are looking at the correct strings    */
  460.     if (time[2]!=':' || date[2]!='/') return((time_t)0);
  461.     
  462.     tms->tm_sec = atoi(time+6);
  463.     tms->tm_min = atoi(time+3);
  464.     tms->tm_hour = atoi(time);
  465.     tms->tm_mday = atoi(date+3);
  466.     tms->tm_mon = atoi(date) - 1;
  467.     tms->tm_year = atoi(date+6);
  468.     tms->tm_isdst = 0;
  469.     
  470.     return(mktime(tms));
  471. }
  472.  
  473. /* ConvDate - convert date of form "03/28/94" to "Mar 28 1994" */
  474. static char *ConvDate(const char *date)
  475. {
  476.     static char        buff[100];
  477.     static char *    months = "JanFebMarAprMayJunJulAugSepOctNovDec";
  478.     
  479.     sprintf(buff,"%3.3s %2d %4d",
  480.             months+3*(atoi(date)-1),
  481.             atoi(date + 3),
  482.             atoi(date + 6) + 1900);
  483.     return(buff);
  484. }
  485.  
  486. /* GetDateStr - get date string from ctime() output */
  487. static void GetDateStr(char *out, char *ctimeStr)
  488. {
  489.     memcpy(out, ctimeStr+4, 7);            // copy month and day
  490.     memcpy(out+7, ctimeStr+20, 4);        // copy year
  491.     out[11] = 0;                        // null terminate it
  492. }
  493.  
  494. /* GetNumDays - get the number of days between two time_t values */
  495. static long GetNumDays(time_t start, time_t end)
  496. {
  497.     struct tm    *tms;
  498.     int            day1, year1;
  499.     int            day2, year2;
  500.     long        numDays;
  501.     
  502.     if (start >= end) return(1);        // exit gracefully on bad input
  503.     
  504.     tms   = localtime(&start);
  505.     day1  = tms->tm_yday;
  506.     year1 = tms->tm_year;
  507.     tms   = localtime(&end);
  508.     day2  = tms->tm_yday;
  509.     year2 = tms->tm_year;
  510.     
  511.     numDays = day2 - day1 + 1;
  512.     
  513.     while (year1 < year2) {
  514.         if (year1 % 4 != 0) numDays += 365L;
  515.         else if (year1 % 100 != 0) numDays += 366L;    // leap year
  516.         else numDays += 365L;    // 365 days at turn of century
  517.         ++year1;
  518.     }
  519.     if (numDays < 1) numDays = 1;
  520.     
  521.     return(numDays);
  522. }
  523.     
  524. /*-----------------------------------------------------------------------------------------
  525. ** Comparison routines
  526. */
  527.  
  528. /* CompStat - compare two statistics string names (used in qsort()) */
  529. int CompStat(const void *p1, const void *p2)
  530. {
  531.     char    *str1 = ((Statistic *)p1)->name;
  532.     char    *str2 = ((Statistic *)p2)->name;
  533.  
  534.     return(strcmpi(str1,str2));
  535. }
  536. /* CompStatNum - compare two statistics string names, evaluating numerical addresses */
  537. int CompStatNum(const void *p1, const void *p2)
  538. {
  539.     int        n1, n2;
  540.     char    *str1 = ((Statistic *)p1)->name;
  541.     char    *str2 = ((Statistic *)p2)->name;
  542.  
  543.     if (isdigit(*str1) && isdigit(*str2)) {
  544.     
  545.         for (;;) {
  546.             n1 = atoi(str1);
  547.             n2 = atoi(str2);
  548.             if (n1 == n2) {
  549.                 str1 = strchr(str1,'.');
  550.                 if (!str1) return(0);
  551.                 str2 = strchr(str2,'.');
  552.                 if (!str2) return(0);
  553.                 ++str1;
  554.                 ++str2;
  555.             } else if (n1 < n2) {
  556.                 return(-1);
  557.             } else {
  558.                 return(1);
  559.             }
  560.         }
  561.  
  562.     } else {
  563.     
  564.         return(strcmpi(str1,str2));
  565.     }
  566. }
  567.  
  568. /*--------------------------------------------------------------------------------------
  569. ** StringLookup class methods
  570. */
  571. /* StringLookup - class constructor */
  572. StringLookup::StringLookup(long incr, Boolean part)
  573.              : num(0),maxNum(0),incrNum(incr),partialCompare(part)
  574. {
  575. }
  576.  
  577. /* ~StringLookup - class destructor */
  578. StringLookup::~StringLookup()
  579. {
  580.     long        i;
  581.     
  582.     /* reset name list */
  583.     if (maxNum) {
  584.         for (i=0; i<num; ++i) delete list[i];
  585.         maxNum = num = 0;
  586.         delete list;
  587.     }
  588. }
  589.  
  590. /* AddString - add string to lookup list */
  591. void StringLookup::Add(char *code, char *name)
  592. {
  593.     int        i;
  594.     char    **oldList;
  595.     
  596.     /* expand country list if necessary */
  597.     if (num >= maxNum) {
  598.         oldList = list;
  599.         list = new char *[maxNum + incrNum];
  600.         if (!list) MemoryError();
  601.  
  602.         if (maxNum) {
  603.             memcpy(list, oldList, maxNum * sizeof(char *));
  604.             delete oldList;
  605.         }
  606.         maxNum += incrNum;
  607.     }
  608.     
  609.     /* add country to list */
  610.     i = strlen(code) + strlen(name) + 2;
  611.     list[num] = new char[i];
  612.     if (!list[num]) MemoryError();
  613.     strcpy(list[num],code);
  614.     strcpy(strchr(list[num],'\0')+1,name);
  615.     ++num;
  616. }
  617.  
  618. /* Lookup - look up an entry in the list */
  619. char *StringLookup::Lookup(char *code)
  620. {
  621.     long        i;
  622.     
  623.     /* search for entry in list */
  624.     for (i=0; i<num; ++i) {
  625.         if (partialCompare) {
  626.             if (!strcmpiPart(list[i], code))  return(strchr(list[i],'\0')+1);
  627.         } else {
  628.             if (!strcmpi(list[i], code))  return(strchr(list[i],'\0')+1);
  629.         }
  630.     }
  631.     
  632.     return((char *)0);
  633. }
  634.  
  635. /*-----------------------------------------------------------------------------------------
  636. ** Statistic class methods
  637. */
  638. /* Statistic - constructor */
  639. Statistic::Statistic()  : files(0), bytes(0), name(0)
  640. {
  641.     /* empty method */
  642. }
  643.  
  644. /* ~Statistic - destructor */
  645. Statistic::~Statistic()
  646. {
  647.     if (name) delete name;        // free memory for name
  648. }
  649.  
  650. /* IncrStats - increment statistics counters */
  651. void Statistic::IncrStats(double byte_count)
  652. {
  653.     files++;
  654.     bytes += byte_count;
  655. }
  656.  
  657. /*-----------------------------------------------------------------------------------------
  658. ** StatList class methods
  659. */
  660. /* StatList - constructor */
  661. StatList::StatList(long iSize) : num(0)
  662. {
  663.     incrSize = maxNum = iSize;
  664.     
  665.     stat = new Statistic[iSize];
  666.     
  667.     if (!stat) MemoryError();
  668. }
  669.  
  670. /* ~StatList - destructor */
  671. StatList::~StatList()
  672. {
  673.     /* delete all Statistic objects */
  674.     for (long n=0; n<maxNum; ++n) delete(stat+n);
  675. }
  676.  
  677. /* Write - write list to file */
  678. void StatList::Write(FILE *fp, char *heading)
  679. {
  680.     int            i,len;
  681.     long        n;
  682.     double        totFile, totByte;
  683.     const int    BSIZ = 256;
  684.     char        *name, buff[BSIZ];
  685.     
  686.     len = strlen(heading);
  687.     
  688.     if (len > BSIZ) return;
  689.     
  690.     memset(buff,'-',len);
  691.     buff[len] = 0;
  692.     
  693.     if (!totalFiles) totFile = 1;                // avoid divide by zero
  694.     else             totFile = totalFiles / 100.0;
  695.     
  696.     if (!totalBytes) totByte = 1;
  697.     else             totByte = totalBytes / 100.0;
  698.     
  699.     fprintf(fp,"<pre>\n");
  700.     fprintf(fp,"%*s  Number of    Number of    Percent of  Percent of\n",len,"");
  701.     fprintf(fp, "%s  Files Sent   Bytes Sent   Files Sent  Bytes Sent\n",heading);
  702.     fprintf(fp, "%s  ----------  ------------  ----------  ----------\n",buff);
  703.     
  704.     ++len;
  705.     
  706.     for (n=0; n<num; ++n) {
  707.     
  708.         if ((i=strlen(stat[n].name))<=len || len<3) {
  709.             name = stat[n].name;
  710.         } else {
  711.             strcpy(buff,"...");
  712.             strcat(buff,stat[n].name+i-len+3);
  713.             name = buff;
  714.         }
  715.         
  716.         fprintf(fp,"%-*s %10ld %13.0f    %6.2f      %6.2f\n",
  717.                 len,
  718.                 name,
  719.                 stat[n].files,
  720.                 stat[n].bytes,
  721.                 stat[n].files / totFile,
  722.                 stat[n].bytes / totByte);
  723.     }
  724.     fprintf(fp,"</pre><p>\n");
  725. }
  726.  
  727. /* SearchStat - search list for matching string, creating new entry if not found */
  728. long StatList::SearchStat(char *str)
  729. {
  730.     long        n;
  731.     Statistic    *tempStat;
  732.     
  733.     /* search through stat names (most recent first for speed) */
  734.     for (n=num-1; n>=0; --n) {
  735.         if (!strcmpi(str,stat[n].name)) return(n);
  736.     }
  737.     if (num >= maxNum) {
  738.         tempStat = new Statistic[maxNum + incrSize];
  739.         if (!tempStat) MemoryError();
  740.         for (n=0; n<maxNum; ++n) {
  741.             tempStat[n] = stat[n];
  742.             stat[n].name = 0;        // make sure memory is not deallocated for name
  743.         }
  744.         delete stat;
  745.         stat = tempStat;
  746.         maxNum += incrSize;
  747.     }
  748.     stat[num].name = new char[strlen(str)+1];
  749.     if (!stat[num].name) MemoryError();
  750.     strcpy(stat[num].name,str);
  751.     return(num++);
  752. }
  753.  
  754. /* IncrStats - increment the stats for given string */
  755. void StatList::IncrStats(double byte_count, char *str)
  756. {
  757.     long    n;
  758.     
  759.     n = SearchStat(str);
  760.     
  761.     stat[n].IncrStats(byte_count);    // increment individual sums
  762. }
  763.  
  764. /* IncrTotals - increment total counters */
  765. void StatList::IncrTotals(double byte_count)
  766. {
  767.     totalFiles++;                    // increment totals
  768.     totalBytes += byte_count;
  769. }
  770.  
  771. /* Substitute - substitute names in list */
  772. void StatList::Substitute(char *(*func)(const char *))
  773. {
  774.     char    *pt, *pt2;
  775.     
  776.     for (long n=0; n<num; ++n) {
  777.         pt = func(stat[n].name);        // get new string
  778.         if (pt) {
  779.             pt2 = new char[strlen(pt) + 1];
  780.             if (!pt2) MemoryError();
  781.             strcpy(pt2,pt);                // copy the string
  782.             delete stat[n].name;        // get rid of old string
  783.             stat[n].name = pt2;
  784.         }
  785.     }
  786. }
  787.  
  788. /* Sort - sort the list alphabetically */
  789. void StatList::Sort(CompFunc func)
  790. {
  791.     if (!func) func = CompStat;
  792.     
  793.     qsort(stat, num, sizeof(Statistic), func);
  794. }
  795.  
  796. /* Summarize - print summary of total day statistics */
  797. void StatList::Summarize(FILE *fp, time_t first, time_t last)
  798. {
  799.     char    firstStr[30];
  800.     char    lastStr[30];
  801.     long    num;
  802.     
  803.     num = GetNumDays(first, last);
  804.     
  805.     GetDateStr(firstStr, ctime(&first));
  806.     GetDateStr(lastStr,  ctime(&last));
  807.     
  808.     fprintf(fp,"<h2>Summary for Period: %s to %s</h2>\n", firstStr, lastStr);
  809.     fprintf(fp,"<pre>\n");
  810.     fprintf(fp,"Files Transmitted During Summary Period  %14d\n",totalFiles);
  811.     fprintf(fp,"Bytes Transmitted During Summary Period  %14.0f\n",totalBytes);
  812.     fprintf(fp,"Average Files Transmitted Daily          %14d\n",totalFiles/num);
  813.     fprintf(fp,"Average Bytes Transmitted Daily          %14.0f\n",totalBytes/num);
  814.     fprintf(fp,"</pre><p>\n");
  815. }
  816.  
  817. /*-----------------------------------------------------------------------------------------
  818. ** main program
  819. */
  820. void main()
  821. {
  822.     long            i, line, fileSize;
  823.     long            numExcl = 0, maxExcl = 0;
  824.     long            numCtry = 0;
  825.     char            *logFile = def_log_file;
  826.     char            *outFile = def_out_file;
  827.     char            *fmtFile = def_fmt_file;
  828.     Boolean            messages = FALSE;
  829.     Boolean            long_addr = TRUE;
  830.     Boolean            dnsLookup = FALSE;
  831.     Boolean            isReverse;
  832.     time_t            tt, firstTime, lastTime;
  833.     struct tm        tms;
  834.     char            *pt,*pt2, *pt3, *country, *last_field;
  835.     char            *domain,*file,*time_str,*date_str,*stat_str;
  836.     char            **exclude, **oldExcl;
  837.     const int        BSIZ = 512;
  838.     Str255            curVol;
  839.     short            vRefNum;
  840.     char            rev_domain[BSIZ],buff[BSIZ],dns_name[BSIZ],buf2[BSIZ];
  841.     StringLookup    *countryList, *addressList;
  842.     double            bytes;
  843.     StatList        *byDate, *byHour, *byWeekday, *byDomain, *bySubdomain, *byFile;
  844.     FILE            *fp, *out, *fmt;
  845.     static char        *syntaxFmt = "Syntax error in %s line %ld: %s '%s'\n";
  846.     static char        *delim = " \t\n";
  847.     static char        *weekdays[] = { "Sunday","Monday","Tuesday","Wednesday",
  848.                                     "Thursday","Friday","Saturday" };
  849.  
  850.     firstTime = 0;
  851.     
  852.     /* initialize country list object */
  853.     countryList = new StringLookup(kCountryIncr,TRUE);
  854.     if (!countryList) MemoryError();
  855.     
  856.     /* read config file */
  857.     if ((fp = fopen(config_file,"r")) == 0) {
  858.         printf("Config file %s not found!\nUsing defaults.\n",config_file);
  859.     } else {
  860.         for (line=1; fgets(buff,BSIZ,fp); ++line) {
  861.  
  862.             pt = strtokQuote(buff,delim);
  863.             if (!pt || *pt=='#') continue;
  864.             
  865.             pt2 = strtokQuote(NULL,delim);
  866.             if (!pt2) {
  867.                 printf(syntaxFmt,config_file,line,"No parameters for",pt);
  868.                 continue;
  869.             }
  870.             
  871.             if (!strcmpi(pt,"LOG")) {
  872.                 logFile = new char[strlen(pt2) + 1];
  873.                 if (!logFile) MemoryError();
  874.                 strcpy(logFile, pt2);
  875.             
  876.             } else if (!strcmpi(pt,"OUTPUT")) {
  877.                 outFile = new char[strlen(pt2) + 1];
  878.                 if (!outFile) MemoryError();
  879.                 strcpy(outFile, pt2);
  880.             
  881.             } else if (!strcmpi(pt,"FORMAT")) {
  882.                 fmtFile = new char[strlen(pt2) + 1];
  883.                 if (!fmtFile) MemoryError();
  884.                 strcpy(fmtFile, pt2);
  885.                 
  886.             } else if (!strcmpi(pt,"EXCLUDE")) {
  887.                 if (numExcl >= maxExcl) {
  888.                     oldExcl = exclude;
  889.                     exclude = new char *[maxExcl + kExcludeIncr];
  890.                     if (!exclude) {
  891.                         printf("Too many exclusions\n");
  892.                         MemoryError();
  893.                     }
  894.                     if (maxExcl) {
  895.                         memcpy(exclude, oldExcl, maxExcl * sizeof(char *));
  896.                         delete oldExcl;
  897.                     }
  898.                     maxExcl += kExcludeIncr;
  899.                 }
  900.                 exclude[numExcl] = new char[strlen(pt2) + 1];
  901.                 if (!exclude[numExcl]) MemoryError();
  902.                 strcpy(exclude[numExcl], pt2);
  903.                 ++numExcl;
  904.             
  905.             } else if (!strcmpi(pt,"MESSAGES")) {
  906.                 if (!strcmpi(pt2,"On")) messages = TRUE;
  907.                 
  908.             } else if (!strcmpi(pt,"ADDRESSES")) {
  909.                 if (!strcmpi(pt2,"Short")) long_addr = FALSE;
  910.                 
  911.             } else if (!strcmpi(pt,"DOMAIN")) {
  912.                 pt3 = strchr(pt2,'*');
  913.                 if (pt3) *pt3 = 0;            // get rid of '*' if it exists
  914.                 pt3 = strtokQuote(NULL,delim);
  915.                 if (!pt3) {
  916.                     printf(syntaxFmt,config_file,line,"Too few parameters for",pt);
  917.                     continue;
  918.                 }
  919.                 countryList->Add(pt2, pt3);
  920.                 ++numCtry;
  921.  
  922.             } else if (!strcmpi(pt,"DNSLOOKUP")) {
  923.                 if (!strcmpi(pt2,"On")) dnsLookup = TRUE;
  924.                 
  925.             } else {
  926.                 printf(syntaxFmt,config_file,line,"Unknown keyword",pt);
  927.             }
  928.         }
  929.         fclose(fp);
  930.     }
  931.     
  932.     if (messages) {
  933.         printf("\n*****************************\n");
  934.         printf("**  --- %s ---  **\n",program_name);
  935.         printf("**  A utility for MacHTTP  **\n");
  936.         printf("**     by Phil Harvey      **\n");
  937.         printf("*****************************\n\n");
  938.         if (numCtry) printf("%ld domain name%s registered.\n",numCtry,numCtry==1?"":"s");
  939.         if (numExcl) printf("%ld exclusion%s registered.\n",numExcl,numExcl==1?"":"s");
  940.         if (numCtry || numExcl) printf("\n");
  941.     }
  942.     
  943.     /* open log file */
  944.     if ((fp = fopen(logFile,"r")) == 0) {
  945.         printf("Can't open log file %s !\n\n",logFile);
  946.         printf("The log file name can be specified in WebStat.config.\n");
  947.         printf("If no path is specified, then the log file\n");
  948.         printf("must be in the same folder as the WebStat program.\n");
  949.         exit(1);
  950.     }
  951.     
  952.     /* open format file */
  953.     if ((fmt = fopen(fmtFile,"r")) == 0) {
  954.         printf("Can't open the HTML format file %s !\n\n",fmtFile);
  955.         printf("If no path is specified, then the format file\n");
  956.         printf("must be in the same folder as the WebStat program.\n");
  957.         exit(1);
  958.     }
  959.     
  960.     if (!messages) {
  961.         ToolBoxInit();
  962.         ParamText("\pProcessing log file","\p","\p","\p");
  963.         ShowProgress(0,TRUE);
  964.     }
  965.     
  966.     /* initialize lists */
  967.     byDate        = new StatList(kDateIncr);
  968.     byHour         = new StatList(kHourIncr);
  969.     byWeekday    = new StatList(kWDayIncr);
  970.     byDomain    = new StatList(kDomainIncr);
  971.     bySubdomain = new StatList(kSubDomainIncr);
  972.     byFile        = new StatList(kFileIncr);
  973.     
  974.     /* initialize weekday and hour names */
  975.     for (i=0; i<7; ++i) byWeekday->SearchStat(weekdays[i]);
  976.     for (i=0; i<24; ++i) {
  977.         sprintf(buf2," %.2ld",i);
  978.         byHour->SearchStat(buf2);
  979.     }
  980.  
  981.     /* init MacTCP if doing DNS lookups */
  982.     if (dnsLookup) {
  983.     
  984.         /* get current volume (DNS routines change this!) */
  985.         GetVol(curVol,&vRefNum);
  986.         
  987.         /* initialize DNS lookup cache table */
  988.         addressList = new StringLookup(kDNSListIncr,FALSE);
  989.         if (!addressList) MemoryError();
  990.         if (messages) printf("Initializing MacTCP...\n");
  991.         if (InitNetwork() != noErr) {
  992.             Quit("\pNet init error!\rYou may want to set DNSLOOKUP Off in your WebStat.config file.\0");
  993.         }
  994.     }
  995.         
  996.     /* read the log file */
  997.     if (messages) {
  998.         printf("Reading file %s...\n",logFile);
  999.     } else {
  1000.         /* get size of file */
  1001.         fseek(fp,0L,SEEK_END);
  1002.         fileSize = ftell(fp);
  1003.         if (!fileSize) ++fileSize;        // prevent /0 errors
  1004.         fseek(fp,0L,SEEK_SET);
  1005.     }
  1006.     
  1007.     while (fgets(buff,BSIZ,fp)) {
  1008.     
  1009.         if (!messages) {
  1010.             
  1011.             /* handle events */
  1012.             HandleEvents();
  1013.             
  1014.             /* update the progress indicator */
  1015.             ShowProgress(ftell(fp)*100L/fileSize, FALSE);
  1016.         }
  1017.         
  1018.         date_str = strtok(buff,delim);
  1019.         time_str = strtok(NULL,delim);
  1020.         stat_str = strtok(NULL,delim);
  1021.         domain   = strtok(NULL,delim);
  1022.         file     = strtok(NULL,delim);
  1023.         bytes     = atof(strtok(NULL,delim));
  1024.  
  1025.         /* ignore lines that don't have enough entries */
  1026.         if (!file) continue;
  1027.         
  1028.         /* convert the time and date strings */
  1029.         tt = ConvTime(time_str, date_str, &tms);
  1030.         
  1031.         /* ignore line if the date didn't convert properly */
  1032.         if (!tt) continue;
  1033.         
  1034.         /* save first and last times */
  1035.         if (!firstTime) firstTime = tt;
  1036.         lastTime = tt;
  1037.         
  1038.         /* clean up address string and determine if it is reversed */
  1039.         isReverse = TRUE;
  1040.         if (isdigit(domain[0])) {
  1041.             pt = strstr(domain,".in-addr");        // get rid of ".in-addr.arpa."
  1042.             if (pt) *pt = 0;
  1043.             else isReverse = FALSE;                // NO_DNS option must be on (name not rev)
  1044.         } else {
  1045.             domain[strlen(domain)-1] = 0;        // get rid of trailing '.'
  1046.         }
  1047.         
  1048.         /* reverse the subdomain if necessary */
  1049.         if (isReverse) {
  1050.             last_field = ReverseAddress(domain,rev_domain);
  1051.         } else {
  1052.             strcpy(rev_domain,domain);
  1053.         }
  1054.         
  1055.         /* resolve all numerical addresses if DNSLOOKUP is On */
  1056.         if (dnsLookup && isdigit(rev_domain[0])) {
  1057.         
  1058.             /* does this entry exist in address cache? */
  1059.             pt = addressList->Lookup(rev_domain);
  1060.             
  1061.             /* if not found, consult DNS for translation */
  1062.             if (!pt) {
  1063.                 
  1064.                 if (messages) printf("DNS lookup %s -> ",rev_domain);
  1065.  
  1066.                 /* get resolved address in buf2 */
  1067.                 if (IPAddrToName(GetIPAddress(rev_domain),dns_name) == noErr) {
  1068.                 
  1069.                     dns_name[strlen(dns_name)-1] = 0;        // get rid of trailing '.'
  1070.  
  1071.                     if (messages) printf("%s\n",dns_name);
  1072.                     
  1073.                     /* must reverse the name returned by DNS */
  1074.                     last_field = ReverseAddress(dns_name, buf2);
  1075.                     
  1076.                     /* add to name cache */
  1077.                     addressList->Add(rev_domain, buf2);
  1078.                     
  1079.                     /* copy new name into domain string */
  1080.                     strcpy(rev_domain, buf2);
  1081.                 
  1082.                 } else {
  1083.                 
  1084.                     /* use unresolved name if host unknown */
  1085.                     addressList->Add(rev_domain, rev_domain);
  1086.                     
  1087.                     if (messages) printf("<unknown>\n");
  1088.                 }
  1089.                 
  1090.             } else {
  1091.             
  1092.                 /* use the converted name from the cache */
  1093.                 strcpy(rev_domain, pt);
  1094.             }
  1095.         }
  1096.         
  1097.         /* check for exclusions */
  1098.         for (i=0; i<numExcl; ++i) {
  1099.             if (!strcmpiWild(exclude[i],rev_domain)) break;
  1100.         }
  1101.         if (i != numExcl) continue;
  1102.         
  1103.         /* parse filespec */
  1104.         for (pt=file; *pt; ++pt) {
  1105.             if (*pt==':') {
  1106.                 if (pt[1]) *pt = '/';        // convert ':' to '/'
  1107.                 else *pt = 0;
  1108.             } else if (*pt=='?' || *pt=='$') {
  1109.                 *pt = 0;                    // truncate file names at '?' or '$'
  1110.                 break;
  1111.             }
  1112.         }
  1113.             
  1114.         /* increment totals */
  1115.         StatList::IncrTotals(bytes);
  1116.         
  1117.         /* weekly totals */
  1118.         byWeekday->IncrStats(bytes, weekdays[tms.tm_wday]);
  1119.         
  1120.         /* hourly totals */
  1121.         sprintf(buf2," %.2d",tms.tm_hour);
  1122.         byHour->IncrStats(bytes, buf2);
  1123.         
  1124.         /* daily totals */
  1125.         byDate->IncrStats(bytes, date_str);
  1126.         
  1127.         /* totals by domain */
  1128.         country = countryList->Lookup(rev_domain);
  1129.         if (!country) {
  1130.             if (isdigit(rev_domain[0])) country = "[unresolved]";
  1131.             else country = last_field;
  1132.         }
  1133.         byDomain->IncrStats(bytes, country);
  1134.         
  1135.         /* shorten address if requested for subdomain list */
  1136.         if (!long_addr) {
  1137.             pt = strrchr(rev_domain,'.');
  1138.             if (pt) *pt = 0;
  1139.         }
  1140.  
  1141.         /* totals by subdomain */
  1142.         bySubdomain->IncrStats(bytes, rev_domain);
  1143.         
  1144.         /* totals by file */
  1145.         if (memcmp(stat_str,"OK",2)) pt = "[nonexistent files]";
  1146.         else pt = file;
  1147.         byFile->IncrStats(bytes, pt);
  1148.     
  1149.     }
  1150.     
  1151.     /* close log file */
  1152.     fclose(fp);
  1153.     
  1154.     /* convert date formats */
  1155.     byDate->Substitute(ConvDate);
  1156.     
  1157.     /* sort the stuff */
  1158.     byDomain->Sort();
  1159.     bySubdomain->Sort(CompStatNum);
  1160.     byFile->Sort();
  1161.     
  1162.     /* print results */
  1163.     if (messages) {
  1164.         printf("Writing file %s...\n",outFile);
  1165.     } else {
  1166.         ParamText("\pWriting output file","\p","\p","\p");
  1167.         ShowProgress(100,TRUE);
  1168.     }
  1169.     
  1170.     /* reset current volume if necessary */
  1171.     if (dnsLookup) SetVol(curVol,vRefNum);
  1172.     
  1173.     /* open output file */
  1174.     if ((out = fopen(outFile,"w")) == 0) {
  1175.         Quit("\pCan't open output file!\rAre your sure the output folder exists, and is not locked?\0");
  1176.     }
  1177.     
  1178.     while (fgets(buff, BSIZ, fmt)) {
  1179.     
  1180.         /* interpret special codes in format file */
  1181.         if (buff[0] == '[') {
  1182.             if (!strcmpiPart("[Summary]",buff)) {
  1183.                 time(&tt);
  1184.                 fprintf(out,"Generated by: %s<br>\n",program_name);
  1185.                 fprintf(out,"Last updated: %s<p>\n",ctime(&tt));
  1186.                 StatList::Summarize(out, firstTime, lastTime);
  1187.                 continue;
  1188.             } else if (!strcmpiPart("[Day]",buff)) {
  1189.                 byDate->Write(out,"   Date    ");
  1190.                 continue;
  1191.             } else if (!strcmpiPart("[Hour]",buff)) {
  1192.                 byHour->Write(out,"Time");
  1193.                 continue;
  1194.             } else if (!strcmpiPart("[Weekday]",buff)) {
  1195.                 byWeekday->Write(out,"    Day    ");
  1196.                 continue;
  1197.             } else if (!strcmpiPart("[Domain]",buff)) {
  1198.                 byDomain->Write(out,"        Domain Name        ");
  1199.                 continue;
  1200.             } else if (!strcmpiPart("[Subdomain]",buff)) {
  1201.                 bySubdomain->Write(out,"    Reversed  Subdomain    ");
  1202.                 continue;
  1203.             } else if (!strcmpiPart("[Archive]",buff)) {
  1204.                 byFile->Write(out,"      Archive Section      ");
  1205.                 continue;
  1206.             }
  1207.         }
  1208.         
  1209.         /* no special code found, copy line to the output */
  1210.         fputs(buff,out);
  1211.     }
  1212.     fclose(out);
  1213.     fclose(fmt);
  1214.     
  1215.     /* free up memory */
  1216.     delete byDate;
  1217.     delete byHour;
  1218.     delete byWeekday;
  1219.     delete byDomain;
  1220.     delete bySubdomain;
  1221.     delete byFile;
  1222.     
  1223.     /* delete exclusions */
  1224.     if (numExcl) {
  1225.         for (i=0; i<numExcl; ++i) delete exclude[i];
  1226.         delete exclude;
  1227.     }
  1228.     
  1229.     /* free country list */
  1230.     delete countryList;
  1231.     
  1232.     /* free DNS cache if allocated */
  1233.     if (dnsLookup) delete addressList;
  1234.  
  1235.     if (messages) {
  1236.         printf("Done.\n");
  1237.     } else {
  1238.         ShowProgress(-1,TRUE);
  1239.     }
  1240. }